package org.xingole.jgit.lib;

/**
 * A SHA-1 abstraction
 * 
 * The simulation to {@code AnyObjectId} class in the jgit project. 
 */
public abstract class AnyObjectId implements Comparable<AnyObjectId> {
    /**
     * We should provide the class method to compares the object identifier -> sha-1 value 
     * for equality. 
     * 
     * @param firstObjectId - the first identifier to compare. not be null.
     * @param secondObjectId - the second identifer to compare. not be null.
     */
    public static boolean equals(final AnyObjectId firstObjectId,
                                 final AnyObjectId secondObjectId) {
        // We should determine the equalty from the JVM aspect ==>> The Java object reference                            
        if(firstObjectId == secondObjectId)
            return true;

        // Otherwise, we should determine whether the every field of the ObjectId is the same
        // The jgit team finds that the first two words namely w0 field are very likely to be 
        // identical. Therefore, the sequence is not from w0 to w4:
        return firstObjectId.w1 == secondObjectId.w1
               && firstObjectId.w2 == secondObjectId.w2
               && firstObjectId.w3 == secondObjectId.w3
               && firstObjectId.w4 == secondObjectId.w4
               && firstObjectId.w0 == secondObjectId.w0;
    }

    /* The 40-characters SHA-1 valuse is modeled by these 4 int type field:
     *  w1, w2, w3, w4, w5  
     * 
     * If the SHA-1 value is  f9dcafd1454c80bc0318e6f5432709cd13cf49d1, then 
     *  w5 = reversed f9dcafd1 w4 = reversed 454c80bc  w3 = reversed 0318e6f5 w2 = reversed 432709cd w1 = reversed 13cf49d1
     * 
     * Note: Important, every w is reversed sequence by character not the result you has seen from the displayer!! 
     * 
     * Oh! I should set the field to be protected access, only allowing the subclass to have access to fetch it.
     * 
     * em... jgit use the field access to get these fields so we should set these field as packaged accessor.
     *
     * one SHA-1 value need 5 word equal to 5 * 32(160) bits.
     */
    int w0;
    
    int w1;

    int w2;

    int w3;

    int w4;

    /**
     * Get the first 8 bits of the ObjectId (the right reversed 4 byte chars of the fact SHA-1).
     * @return a discriminator usable for a fan-out style map.
     */
    public final int getFirstByte() {
        return w0 >>> 24;
    }

    /**
     * Get any byte at the given index from the ObjectId. <br/> 
     * <strong>We must note the byte of the ObjectId, not the byte of the fact SHA-1 string!!!</strong>
     * @param index - index of the byte to obtain from the raw form of the ObjectId. Must be in 
     * range [0, 40).
     * @return the value of the requested byte at index.Returned values are unsigned and thus
     * are in the range [0, 255] (per byte is equal with 8 bits) rather than the signed byte
     * range of [-128, 127].
     * @throws ArrayIndexOutOfBoundsException - index is less than 0, equal to or greate than 
     * {@code Constants.OBJECT_ID_LENGTH}.
     */
    public final int getByte(int index) {
        /*
         * The AnyObjectId byte array, every element represent the SHA-1 double chars: 
         *               index  =>  floor(index/4) is the first index of the different area
         * --------------|---------------------------------------------------
         * -0-------3-4--------7--8--------11-12-------15-16-------19--------
         * |   w0  | |   w1    | |   w2    |  |  w3    |  |   w4   |
         * ------------------------------------------------------------------
         * wi is the Least Significant Bit, namely the least significant bit(the most left bit) 
         * corresponds the lower index in the ObjectId string.
         */
        // the local variable to save the field of the index position 
        int w;
        switch (index >> 2) {
            case 0:
                w = w0;
                break;
            case 1:
                w = w1;
                break;
            case 2:
                w = w2;
                break;
            case 3:
                w = w3;
                break;
            case 4:
                w = w4;
                break;
            default:
                throw new ArrayIndexOutOfBoundsException(index);
        }
        // Then, we must evaluate the byte double chars at the given index.
        /*
         *       | offset |         
         *      low     index       high
         *  -----|-------|-----------|-------
         *      ws3    ws2    ws1   ws0
         * -------------|---w-------|---------
         *             |index in w  |
         * 
         * To improve the code effects, we can use bit operators to promote the execution speed.
         * 
         * offset = index mod 4 =  index & 3 (011)
         * index in w = 3 - offset
         * 
         * Last but not least, the unit above is byte (8 bits) - we should note the fact.
         * 
         * Therefore, the ultimate result is w >>> (8 * (index in w)) ==>> 0x00 0x00 ws3 ws2.
         * The last thing is to do (result & 0xff).
         * 
         * That's all about my understanding for the source code of the jgit project.
         */
        return (w >>> (8 * (3 - (index & 3)))) & 0xff;
    }
    /****************The methods related with SHA-1 value comparation****************************/

    @Override
    public int compareTo(final AnyObjectId other) {
        if (this == other)
            return 0;   // 0: the value is same with each other.

        
        // noop implemented
        return 0;
    }


    /****************The methods related with SHA-1 string name format**************************/

    @Override
    public String toString() {
        return "AnyObjectId[" + name() + "]";
    }

    /**
     * Although this method has the sanme effect with {@name} method, 
     * the method make the {@AnyObjectId} class with the JavaBean property:  
     * name. 
     * Therefore, we should apply this fashion to our owner Java application.
     * 
     * The advantage of the fashion:
     * <ul>
     *  <li>The {@code getName} method can be used by user familiar with JavaBean.</li>
     *  <li>The {@code name} method can be used by programmer themselves.</li>
     * </ul> 
     * @return string form of the SHA-1 hash value, in lower case hexadecimal
     */
    public final String getName() {
        return name();
    }

    /**
     * @return string form of the SHA-1 hash value, in lower case hexadecimal
     */
    public final String name() {
        return new String(toHexCharArray());
    }

    private char[] toHexCharArray() {
        // Now, we should replace the specific value with the Java constants!!
        final char[] dst = new char[Constants.OBJECT_ID_STRING_LENGTH];
        toHexCharArray(dst);
        return dst;
    }

    private void toHexCharArray(final char[] dst) {
        /*
         * Assumpts the dst array is divide as followed areas:
         * 
         * dst char array is like:         
         * 
         * --right--<<<The SHA-1 string extends from this direction<<<<-left-
         * -0-------7-8--------15-16-------23-24------31-32------39----------
         * |   w0  | |   w1    | |   w2    | |  w3    | |   w4   |
         * ------------------------------------------------------------------
         * 
         * In this method, we only need to call formatHexChar method to fill the 
         * related filed w.  
         * 
         * wi is the Least Significant Bit, namely the least significant bit(the most left bit) 
         * corresponds the lower index in the ObjectId string.
         * 
         * That is all, OK!    
         */
        formatHexChar(dst, 0, w0);
		formatHexChar(dst, 8, w1);
		formatHexChar(dst, 16, w2);
		formatHexChar(dst, 24, w3);
		formatHexChar(dst, 32, w4);
    }

    private static final char[] hexchar = {'0', '1', '2', '3', '4', '5', '6',
            '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

    /**
     * formate the hex character, processing the single char
     * @param dst - the destination array
     * @param p - the offset position of the filled field, the unit is per char
     * @param w - the coming filling field
     */
    static void formatHexChar(final char[] dst, final int p, int w) {
        int o = p + 7;  // o: the upper index for w; p: the lower index for w
        
        // fill the char array for the high to low
        while(o >= p && w != 0) {
            dst[o--] = hexchar[w & 0xf];  /* yield the lower 4 bits of the w */
            w >>>= 4;  // oh! the sequende is revesed, so the generated w must revese the fact w.
        }

        // ha! we don't forgets appends the invisible 0 from the reversion effect sides.
        while(o >= p)
            dst[o--] = '0';
    }
}
